package com.atguigu.tingshu.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.account.AccountFeignClient;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.KafkaConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.delay.DelayMsgService;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.model.order.OrderDerate;
import com.atguigu.tingshu.model.order.OrderDetail;
import com.atguigu.tingshu.model.order.OrderInfo;
import com.atguigu.tingshu.model.user.VipServiceConfig;
import com.atguigu.tingshu.order.helper.SignHelper;
import com.atguigu.tingshu.order.mapper.OrderDerateMapper;
import com.atguigu.tingshu.order.mapper.OrderDetailMapper;
import com.atguigu.tingshu.order.mapper.OrderInfoMapper;
import com.atguigu.tingshu.order.service.OrderInfoService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.atguigu.tingshu.vo.order.OrderDerateVo;
import com.atguigu.tingshu.vo.order.OrderDetailVo;
import com.atguigu.tingshu.vo.order.OrderInfoVo;
import com.atguigu.tingshu.vo.order.TradeVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Array;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Autowired
    private OrderDerateMapper orderDerateMapper;

    @Autowired
    private AccountFeignClient accountFeignClient;

    @Autowired
    private DelayMsgService delayMsgService;


    /**
     * 订单确认页面数据渲染-展示“商品”结算页
     *
     * @return
     */
    @Override
    public OrderInfoVo orderTrade(Long userId, TradeVo tradeVo) {
        //1.根据用户ID远程调用用户服务获取用户信息（服务提供方可能从Redis缓存）
        UserInfoVo userInfoVo = userFeignClient.getUserInfoVoById(userId).getData();
        Assert.notNull(userInfoVo, "用户信息为空！");

        //2.创建订单信息VO对象 初始化价格（原价、减免价、订单价、商品清单列表、优惠列表）
        OrderInfoVo orderInfoVo = new OrderInfoVo();
        orderInfoVo.setItemType(tradeVo.getItemType());
        //TODO这里金额必须只能是 "0.00" 导致提交订单验签失败
        BigDecimal originalAmount = new BigDecimal("0.00");
        BigDecimal derateAmount = new BigDecimal("0.00");
        BigDecimal orderAmount = new BigDecimal("0.00");

        //声明订单商品明细列表
        List<OrderDetailVo> orderDetailVoList = new ArrayList<>();
        //声明订单优惠明细列表
        List<OrderDerateVo> orderDerateVoList = new ArrayList<>();

        //3.处理订单确认数据-VIP会员 购买项目类型：1003-vip会员 可以重复购买
        if (SystemConstant.ORDER_ITEM_TYPE_VIP.equals(tradeVo.getItemType())) {
            //3.1 远程调用“用户服务”获取预购套餐信息 得到各种价格
            VipServiceConfig serviceConfig = userFeignClient.getVipServiceConfig(tradeVo.getItemId()).getData();
            Assert.notNull(serviceConfig, "套餐不存在！");

            //3.2 为订单中各种价格赋值（原价、减免价、订单价）
            originalAmount = serviceConfig.getPrice();
            orderAmount = serviceConfig.getDiscountPrice();
            derateAmount = originalAmount.subtract(orderAmount);

            //3.3 封装订单包含商品明细列表
            OrderDetailVo orderDetailVo = new OrderDetailVo();
            orderDetailVo.setItemId(tradeVo.getItemId());
            orderDetailVo.setItemName(serviceConfig.getName());
            orderDetailVo.setItemUrl(serviceConfig.getImageUrl());
            //订单中明细商品价格：展示原价
            orderDetailVo.setItemPrice(originalAmount);
            orderDetailVoList.add(orderDetailVo);

            //3.4 封装订单包含优惠明细列表
            OrderDerateVo orderDerateVo = new OrderDerateVo();
            orderDerateVo.setDerateType(SystemConstant.ORDER_DERATE_VIP_SERVICE_DISCOUNT);
            orderDerateVo.setDerateAmount(derateAmount);
            orderDerateVo.setRemarks("VIP套餐限时优惠：" + derateAmount);
            orderDerateVoList.add(orderDerateVo);


        }
        else if (SystemConstant.ORDER_ITEM_TYPE_ALBUM.equals(tradeVo.getItemType())) {
            //4. 处理订单确认数据-专辑
            //4.1 根据用户ID跟预购专辑ID判断是否重复购买
            Boolean ifBuy = userFeignClient.isPaidAlbum(tradeVo.getItemId()).getData();
            if (ifBuy) {
                throw new GuiguException(ResultCodeEnum.REPEAT_BUY_ERROR);
            }
            //4.2 远程调用专辑服务获取专辑信息（服务提供方可能从Redis缓存）得到专辑价格跟折扣
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(tradeVo.getItemId()).getData();
            Assert.notNull(albumInfo, "专辑不存在！");
            //4.3 为不同身份用户计算价格（原价、订单价、减免价）
            //4.3.1 获取专辑原价
            originalAmount = albumInfo.getPrice();
            orderAmount = originalAmount;
            //4.3.2 处理普通用户折扣
            if (albumInfo.getDiscount().intValue() != -1) {
                //普通用户打折
                if (userInfoVo.getIsVip().intValue() == 0 || new Date().after(userInfoVo.getVipExpireTime())) {
                    //除以 避免精度丢失=保留2位小数同时四舍五入
                    orderAmount = originalAmount
                            .multiply(albumInfo.getDiscount())
                            .divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP);
                    derateAmount = originalAmount.subtract(orderAmount);
                }
            }

            //4.3.3 处理VIP用户折扣
            if (albumInfo.getVipDiscount().intValue() != -1) {
                //普通用户打折
                if (userInfoVo.getIsVip().intValue() == 1 && new Date().before(userInfoVo.getVipExpireTime())) {
                    //除以 避免精度丢失=保留2位小数同时四舍五入
                    orderAmount = originalAmount
                            .multiply(albumInfo.getVipDiscount())
                            .divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP);
                    derateAmount = originalAmount.subtract(orderAmount);
                }
            }
            //4.4 封装订单中商品明细列表（专辑）
            OrderDetailVo orderDetailVo = new OrderDetailVo();
            orderDetailVo.setItemId(tradeVo.getItemId());
            orderDetailVo.setItemName(albumInfo.getAlbumTitle());
            orderDetailVo.setItemUrl(albumInfo.getCoverUrl());
            orderDetailVo.setItemPrice(originalAmount);
            orderDetailVoList.add(orderDetailVo);

            //4.5 封装订单中商品减免明细列表
            OrderDerateVo orderDerateVo = new OrderDerateVo();
            orderDerateVo.setDerateType(SystemConstant.ORDER_DERATE_ALBUM_DISCOUNT);
            orderDerateVo.setDerateAmount(derateAmount);
            orderDerateVo.setRemarks("专辑折扣，减免：" + derateAmount);
            orderDerateVoList.add(orderDerateVo);
        }
        else if (SystemConstant.ORDER_ITEM_TYPE_TRACK.equals(tradeVo.getItemType())) {
            //5.TODO 处理订单确认数据-声音
            //5.1 远程调用“专辑服务”携带声音ID+购买集数 查询用户代购买声音列表（将已购买声音排除掉） LIst<TrackInfo>
            List<TrackInfo> waitBuyTrackList = albumFeignClient.getUserWaitPayTrackInfoList(tradeVo.getItemId(), tradeVo.getTrackCount()).getData();
            Assert.notNull(waitBuyTrackList, "无可购买声音!");

            //5.2 远程调用“专辑服务”根据购买声音所属专辑ID查询专辑信息 得到声音价格（不支持折扣）
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(waitBuyTrackList.get(0).getAlbumId()).getData();
            Assert.notNull(albumInfo, "参数异常！");


            //5.3 计算价格 单价*集数=原始价格/订单价格
            BigDecimal price = albumInfo.getPrice();
            originalAmount = price.multiply(BigDecimal.valueOf(waitBuyTrackList.size()));
            orderAmount = originalAmount;

            //5.4 封装订单中商品明细列表（声音列表）
            orderDetailVoList = waitBuyTrackList.stream().map(trackInfo -> {
                OrderDetailVo orderDetailVo = new OrderDetailVo();
                orderDetailVo.setItemId(trackInfo.getId());
                orderDetailVo.setItemName(trackInfo.getTrackTitle());
                orderDetailVo.setItemUrl(trackInfo.getCoverUrl());
                orderDetailVo.setItemPrice(price);
                return orderDetailVo;
            }).collect(Collectors.toList());
        }

        //6.对订单VO中剩余其他属性统一处理：流水号、签名、时间戳  用户没选择：付款方式不处理
        orderInfoVo.setOrderAmount(orderAmount);
        orderInfoVo.setOriginalAmount(originalAmount);
        orderInfoVo.setDerateAmount(derateAmount);
        orderInfoVo.setOrderDetailVoList(orderDetailVoList);
        orderInfoVo.setOrderDerateVoList(orderDerateVoList);
        //6.1 避免用户对同一订单重复多次提交，渲染订单确认页面时候生成流水号
        String tradeNoKey = RedisConstant.ORDER_TRADE_NO_PREFIX + userId;
        String tradeNo = IdUtil.randomUUID();
        //存入服务端Redis中
        redisTemplate.opsForValue().set(tradeNoKey, tradeNo, RedisConstant.ORDER_TRADE_EXPIRE, TimeUnit.MINUTES);
        orderInfoVo.setTradeNo(tradeNo);

        //6.2 避免风险订单确认页面任意参数及值发生变化，基于签名机制-生成签名
        orderInfoVo.setTimestamp(DateUtil.current());
        //调用工具方法基于现有参数生成签名： TODO 订单VO信息中以及签名参数Map不包含：支付方式
        Map<String, Object> paramsMap = BeanUtil.beanToMap(orderInfoVo, false, true);
        String sign = SignHelper.getSign(paramsMap);
        orderInfoVo.setSign(sign);
        return orderInfoVo;
    }

    /**
     * 提交订单
     *
     * @param userId
     * @param orderInfoVo
     * @return
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public String submitOrder(Long userId, OrderInfoVo orderInfoVo) {
        //1.验证流水号，订单是否重复提交
        //1.1 先获取服务器端存放在Redis中流水号
        String tradeNoKey = RedisConstant.ORDER_TRADE_NO_PREFIX + userId;
        //1.2 跟前端提交流水比较，比对成功删除流水号（采用Lua脚本删除|）
        String script = "if(redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Boolean.class);
        Boolean flag = (Boolean) redisTemplate.execute(redisScript, Arrays.asList(tradeNoKey), orderInfoVo.getTradeNo());
        if (!flag) {
            throw new GuiguException(ResultCodeEnum.ORDER_SUBMIT_REPEAT);
        }

        //2.验证订单签名，避免订单页面数据被篡改
        //2.1 先将VO转为Map 当时签名时候“支付方式”没有参与签名 单前端提交
        Map<String, Object> paramMap = BeanUtil.beanToMap(orderInfoVo);
        //2.2 固将支付方式参数从Map中移除掉
        paramMap.remove("payWay");
        //2.3 调用工具方法进行验证签名
        SignHelper.checkSign(paramMap);

        //3.新增订单（订单，订单明细，优惠明细） 返回订单对象
        OrderInfo orderInfo = this.saveOrder(userId, orderInfoVo);

        //4.判断如果是余额付款则处理（扣减余额、新增用户购买记录）
        if (SystemConstant.ORDER_PAY_ACCOUNT.equals(orderInfoVo.getPayWay())) {
            //4.1 远程调用“账户服务”扣减账户余额
            AccountLockVo accountDeductVo = new AccountLockVo();
            accountDeductVo.setOrderNo(orderInfo.getOrderNo());
            accountDeductVo.setUserId(userId);
            accountDeductVo.setAmount(orderInfo.getOrderAmount());
            accountDeductVo.setContent(orderInfo.getOrderTitle());
            Result deductResult = accountFeignClient.checkAndDeduct(accountDeductVo);
            if (200 != deductResult.getCode()) {
                //扣减余额失败：全局事务都需要回滚
                throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
            }
            //4.2 远程调用“用户服务”新增购买记录
            UserPaidRecordVo userPaidRecordVo = new UserPaidRecordVo();
            userPaidRecordVo.setOrderNo(orderInfo.getOrderNo());
            userPaidRecordVo.setUserId(userId);
            userPaidRecordVo.setItemType(orderInfo.getItemType());
            List<Long> itemIdList = orderInfoVo.getOrderDetailVoList().stream().map(OrderDetailVo::getItemId).collect(Collectors.toList());
            userPaidRecordVo.setItemIdList(itemIdList);

            Result paidRecordResult = userFeignClient.savePaidRecord(userPaidRecordVo);
            if (200 != paidRecordResult.getCode()) {
                //新增购买记录失败：全局事务都需要回滚
                throw new GuiguException(211, "新增购买记录异常");
            }
            //5.余额扣减成功、购买记录新增成功 更新订单支付状态：已支付
            orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_PAID);
            orderInfoMapper.updateById(orderInfo);
        }
        //7.发送延迟消息-完成订单延迟关单
        delayMsgService.sendDelayMessage(KafkaConstant.QUEUE_ORDER_CANCEL, orderInfo.getId().toString(), 600);
        //6.响应新增订单后订单编号（全局唯一）
        return orderInfo.getOrderNo();
    }

    /**
     * 保存订单相关信息
     *
     * @param userId      用户ID
     * @param orderInfoVo 订单VO信息
     * @return
     */
    @Override
    public OrderInfo saveOrder(Long userId, OrderInfoVo orderInfoVo) {
        //1.保存订单信息
        //1.1 将提交订单vo属性拷贝到订单PO对象中
        OrderInfo orderInfo = BeanUtil.copyProperties(orderInfoVo, OrderInfo.class);
        //1.2 生成订单编号（全局唯一趋势递增） 日期+趋势递增ID（分布式ID生成：雪花算法/Tinyid(滴滴)/Leaf(美团)/UidGenerator(百度)）
        String orderNo = DateUtil.today().replaceAll("-", "") + IdUtil.getSnowflakeNextId();
        orderInfo.setOrderNo(orderNo);
        //1.3 获取购买项第一商品名称给订单标题复制
        orderInfo.setOrderTitle(orderInfoVo.getOrderDetailVoList().get(0).getItemName());
        //1.4 初始订单状态：未支付 0901
        orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_UNPAID);
        //1.5 用户ID
        orderInfo.setUserId(userId);
        //1.6 保存订单
        orderInfoMapper.insert(orderInfo);
        //获取订单ID
        Long orderId = orderInfo.getId();

        //2.新增订单明细列表
        List<OrderDetailVo> orderDetailVoList = orderInfoVo.getOrderDetailVoList();
        if (CollectionUtil.isNotEmpty(orderDetailVoList)) {
            orderDetailVoList.stream().forEach(orderDetailVo -> {
                //将VO转为PO对象
                OrderDetail orderDetail = BeanUtil.copyProperties(orderDetailVo, OrderDetail.class);
                //关联订单ID
                orderDetail.setOrderId(orderId);
                //保存订单明细
                orderDetailMapper.insert(orderDetail);
            });
        }

        //3.新增订单优惠列表
        List<OrderDerateVo> orderDerateVoList = orderInfoVo.getOrderDerateVoList();
        if (CollectionUtil.isNotEmpty(orderDerateVoList)) {
            orderDerateVoList.stream().forEach(orderDerateVo -> {
                //将VO转为PO对象
                OrderDerate orderDerate = BeanUtil.copyProperties(orderDerateVo, OrderDerate.class);
                //关联订单ID
                orderDerate.setOrderId(orderId);
                //保存优惠明细
                orderDerateMapper.insert(orderDerate);
            });
        }
        return orderInfo;
    }

    /**
     * 根据订单编号查询订单详情(订单商品明细、订单优惠明细)
     *
     * @param orderNo
     * @return
     */
    @Override
    public OrderInfo getOrderInfo(String orderNo) {
        //1.根据订单编号查询订单信息
        LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderInfo::getOrderNo, orderNo);
        OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);
        if (orderInfo != null) {
            //2.跟订单ID查询订单明细列表
            LambdaQueryWrapper<OrderDetail> orderDetailLambdaQueryWrapper = new LambdaQueryWrapper<>();
            orderDetailLambdaQueryWrapper.eq(OrderDetail::getOrderId, orderInfo.getId());
            List<OrderDetail> orderDetailList = orderDetailMapper.selectList(orderDetailLambdaQueryWrapper);
            orderInfo.setOrderDetailList(orderDetailList);

            //3.跟订单ID查询订单优惠列表
            LambdaQueryWrapper<OrderDerate> orderDerateLambdaQueryWrapper = new LambdaQueryWrapper<>();
            orderDerateLambdaQueryWrapper.eq(OrderDerate::getOrderId, orderInfo.getId());
            List<OrderDerate> orderDerateList = orderDerateMapper.selectList(orderDerateLambdaQueryWrapper);
            orderInfo.setOrderDerateList(orderDerateList);

            //4.设置订单支付状态及支付方式：中文
            orderInfo.setOrderStatusName(this.getOrderStatusName(orderInfo.getOrderStatus()));
            orderInfo.setPayWayName(this.getPayWayName(orderInfo.getPayWay()));
            return orderInfo;
        }
        return null;
    }

    /**
     * 分页获取当前用户订单列表（包含订单商品明细）
     *
     * @param pageInfo
     * @param userId
     */
    @Override
    public void getUserOrderByPge(Page<OrderInfo> pageInfo, Long userId) {
        //调用持久层Mapper进行分页条件查询
        pageInfo = orderInfoMapper.getUserOrderByPge(pageInfo, userId);

        //分页单独查询订单列表，再遍历订单，根据订单ID查询订单明细
    }


    /**
     * 关闭订单
     *
     * @param orderId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void orderCancel(String orderId) {
        //1.根据订单ID查询订单信息
        OrderInfo orderInfo = orderInfoMapper.selectById(orderId);
        if (orderInfo != null && SystemConstant.ORDER_STATUS_UNPAID.equals(orderInfo.getOrderStatus())) {
            //2.判断是否满足订单关闭要求，订单为未支付状态
            orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_CANCEL);
            orderInfoMapper.updateById(orderInfo);
            log.info("[订单服务]，将未支付订单：{}，取消。", orderId);
        }
    }

    /**
     * 修改订单支付状态以及新增购买记录
     *
     * @param orderNo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void orderPaySuccess(String orderNo) {
        //1.根据订单编号查询订单记录 判断订单支付状态 如果是已支付则返回即可
        LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderInfo::getOrderNo, orderNo);
        OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);
        if (orderInfo != null && SystemConstant.ORDER_STATUS_PAID.equals(orderInfo.getOrderStatus())) {
            return;
        }
        //2.修改订单状态：改为已支付
        orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_PAID);
        orderInfoMapper.updateById(orderInfo);

        //3.远程调用用户服务新增购买记录
        //3.1 构建购买记录VO对象
        UserPaidRecordVo userPaidRecordVo = new UserPaidRecordVo();
        userPaidRecordVo.setOrderNo(orderNo);
        userPaidRecordVo.setUserId(orderInfo.getUserId());
        userPaidRecordVo.setItemType(orderInfo.getItemType());
        //3.2 查询订单明细封装购买项目ID
        LambdaQueryWrapper<OrderDetail> orderDetailLambdaQueryWrapper = new LambdaQueryWrapper<>();
        orderDetailLambdaQueryWrapper.eq(OrderDetail::getOrderId, orderInfo.getId());
        List<OrderDetail> orderDetailList = orderDetailMapper.selectList(orderDetailLambdaQueryWrapper);
        if (CollectionUtil.isNotEmpty(orderDetailList)) {
            List<Long> itemIdList = orderDetailList.stream().map(OrderDetail::getItemId).collect(Collectors.toList());
            userPaidRecordVo.setItemIdList(itemIdList);
        }
        Result result = userFeignClient.savePaidRecord(userPaidRecordVo);
        if (200 != result.getCode()) {
            throw new GuiguException(212, "新增购买记录异常");
        }
    }

    private String getOrderStatusName(String orderStatus) {
        if (SystemConstant.ORDER_STATUS_UNPAID.equals(orderStatus)) {
            return "未支付";
        } else if (SystemConstant.ORDER_STATUS_PAID.equals(orderStatus)) {
            return "已支付";
        } else if (SystemConstant.ORDER_STATUS_CANCEL.equals(orderStatus)) {
            return "取消";
        }
        return null;
    }

    /**
     * 根据支付方式编号得到支付名称
     *
     * @param payWay
     * @return
     */
    private String getPayWayName(String payWay) {
        if (SystemConstant.ORDER_PAY_WAY_WEIXIN.equals(payWay)) {
            return "微信";
        } else if (SystemConstant.ORDER_PAY_ACCOUNT.equals(payWay)) {
            return "余额";
        } else if (SystemConstant.ORDER_PAY_WAY_ALIPAY.equals(payWay)) {
            return "支付宝";
        }
        return "";
    }
}
